package com.demo.filter;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
import com.demo.common.ResultData;
import com.demo.config.I18nHelper;
import com.demo.exception.BizException;
import com.demo.log.LogHelper;
import com.demo.util.IpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.MDC;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * @author wangfengchen
 */
@Slf4j
@RequiredArgsConstructor
public class ReqContextFilter extends OncePerRequestFilter {

  public static final String TRACE_ID = "traceId";

  private List<String> excludes = new ArrayList<>();

  public void setExcludes(List<String> excludes) {
    this.excludes = excludes;
  }

  private final FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
  private final I18nHelper i18nHelper;


  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
    String requestId = IdUtil.getSnowflakeNextIdStr();
    MDC.put(TRACE_ID, requestId);
    String ip = IpUtil.getIpAddr(request);
    String url = request.getRequestURL().toString();
    log.info("进入【ReqContextFilter】，ip = {}，url = {}", ip, url);
    RequestContext requestContext = new RequestContext();
    requestContext.setRequestId(requestId);
    RequestContextHelper.set(requestContext);
    LogHelper logHelper = LogHelper.get();
    logHelper.setIp(ip);
    logHelper.setUrl(url);
    try {
      // 解决@RequestBody使用的是流获取输出的参数，导致第二次读取不到，所以需要重新放进去
      String contentType = request.getContentType();
      // 如果是application/x-www-form-urlencoded, 参数值在request body中以 a=1&b=2&c=3 形式存在，
      // 若直接构造BodyReaderHttpServletRequestWrapper，在将流读取并存到copy字节数组里之后，request.getParameterMap()将返回空值！
      // 若运行一下 request.getParameterMap()，body中的流将为空! 所以两者是互斥的！
      if (contentType != null && contentType.contains("application/x-www-form-urlencoded")) {
        request.getParameterMap();
      }
      // 判断类型如果是multipart类型，则将request手动封装为multipart类型，否则上传请求获取不到参数
      if (contentType != null && contentType.contains("multipart/form-data")) {
        request = new StandardServletMultipartResolver().resolveMultipart(request);
      }
      if (!(request instanceof CustomRequestWrapper)) {
        request = new CustomRequestWrapper(request);
      }
      chain.doFilter(request, response);
    } catch (Exception e) {
      log.error("requestId = {}，发生错误，e = {}", requestId, ExceptionUtils.getStackTrace(e));
      ResultData<Object> result = e instanceof BizException ? ResultData.fail(e.getMessage()) : ResultData.fail(i18nHelper.getMessage("system.error"));
      write(response, result);
    } finally {
      MDC.clear();
      RequestContextHelper.remove();
      LogHelper.remove();
    }
  }


  /**
   * 响应错误数据
   *
   * @param resp
   * @param error
   */
  private void write(HttpServletResponse resp, Object error) {
    try {
      fastJsonHttpMessageConverter.write(error, MediaType.APPLICATION_JSON,
          new ServletServerHttpResponse(resp));
    } catch (IOException ex) {
      log.error("response io exception: ", ex);
    }
  }

  /**
   * 根据错误码响应错误数据
   *
   * @param req
   * @param resp
   * @param code
   */
  private void write(HttpServletRequest req, HttpServletResponse resp, int code) {
    ResultData<Void> result = ResultData.fail(code, i18nHelper.getMessage(req, code));
    try {
      fastJsonHttpMessageConverter.write(result, MediaType.APPLICATION_JSON,
          new ServletServerHttpResponse(resp));
    } catch (IOException ex) {
      log.error("response io exception: ", ex);
    }
  }

  /**
   * 不需要过滤的请求
   *
   * @param request
   * @return
   */
  @Override
  protected boolean shouldNotFilter(HttpServletRequest request) {
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    return excludes.stream().anyMatch(pattern -> antPathMatcher.match(pattern, request.getRequestURI()));
  }


}
